#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <api/bidk_gettid.h>
#include <execinfo.h>
#include <fcntl.h>
#include <unistd.h>
#include "api/bidk_plugin_support.h"
#include "api/utils/bidk_backtrace_dump.h"
#include "api/utils/crash_report.h"
#include "bi/bidk.h"
#include "config.h"

extern "C" {
void print_crash_report(int signum , const siginfo_t* info, const ucontext_t* context, const bidk_thread* thread_data)
{
    crash_report_t report(signum, info, context, thread_data);

    report.write_header();
    report.flush();

    report.write_siginfo();
    report.flush();

    report.write_registers();
    report.flush();

    report.write_cc_statistic();
    report.flush();

    report.write_context_specific_info();
    report.flush();

    report.write_app_backtrace();
    report.flush();

}    
}

crash_report_t::crash_report_t(int signum, const siginfo_t* info, const ucontext_t* context,
                               const bidk_thread* thread_data)
    : signum_(signum),
      siginfo_(info),
      context_(context),
      thread_data_(thread_data)
{
    create_crash_report_file();
}      

void crash_report_t::create_crash_report_file() noexcept
{
    auto tid = bidk_gettid();
    auto timestamp = time(nullptr);

    put("/tmp/dbitools/logs/crash_report_", tid, '_', timestamp, ".txt");

    mkdir("/tmp/dbitools", 0755);
    mkdir("/tmp/dbitools/logs", 0755);
    auto fd = open(buffer_, O_WRONLY | O_CREAT | O_TRUNC, 0644);
    if (fd != -1)
    {
        output_fd_ = fd;
    }

    p_ = buffer_;
    
}

crash_report_t::~crash_report_t()
{
    flush();
    if (output_fd_ != STDERR_FILENO) {
        print_file_info();
        close(output_fd_);
    }
}

void crash_report_t::write_header() noexcept
{
    put("======= CRASH REPORT =======");

    put("Command line: ");
    if (global_data.argc > 0 && global_data.argv) {
        for (int i = 0; i < global_data.argc; ++i)
        {
            if (global_data.argv[i]) {
                put(global_data.argv[i], '');
            }
        }
        
    } else {
        put("(unavailable)");
    }
    put('\n');

    put("Signal: ", signum_, '\n');

    if (!context_ || !thread_data_ || !thread_data_->code_cache) {
        put("Context: Unable to determine")
    }
    
    put('\n');
}

void crash_report_t::write_siginfo() noexcept
{
    put("------- Signal Information");

    if (!siginfo_) {
        put("Signal Info: NULL");
        return;
    }

    put("Signal Number: ", siginfo_->si_signo, '\n');
    put("Signal Nmae: ", signal_name(siginfo_->si_signo), '\n');
    put("Signal Code: ", siginfo_->si_code, '\n');
    put("Code Description", signal_code_name(siginfo_->si_signo, siginfo_->si_code), '\n');

    if (siginfo_->si_code <= 0) {
        put("Current PID: ", syscall(__NR_getpid, '\n'));
        put("Sender PID: ", siginfo_->si_pid, '\n');
        put("Sender UID: ", siginfo_->si_uid, '\n');
    }

    switch (siginfo_->si_signo) {
        case SIGSEGV:
        case SIGBUS:
        case: SIGILL:
        case SIGFPE:
            put("Fault Address: ", hex(bidk::reinterpret<uintptr_t>(siginfo_->si_addr)), '\n');
            break;
        case SIGCHLD:
            put("Child PID: ", siginfo_->si_pid, '\n');
            put("Child UID: ", siginfo_->si_uid, '\n');
            put("Exit Status: ", siginfo_->si_status, '\n');
            break;
    }

    put('\n');
}

void crash_report_t::write_registers() noexcept
{
    put("------- General Registers -------\n");

    if (!context_) {
        put("UContext: NULL\n\n");
        return;
    }

    put("PC: ", hex(context_->uc_mcontext.pc), '\n');
    put("PC: ", hex(context_->uc_mcontext.sp), '\n');
    put("PSTATE: ", hex(context_->uc_mcontext.pstate), '\n\n');

    constexpr int total_registers = 31;
    constexpr int cols = 3;
    constexpr int rows = (total_registers + cols - 1) / cols;

    for (int row = 0;; row < rows; ++row) {
        for (int col = 0; col < cols; ++col)
        {
            int reg_num = row + col + rows;
            if (reg_num < total_registers)
            {
                if (col > 0)
                {
                    put("  ")
                }
                put('x', reg_num, ": ", hex(context_->uc_mcontext.regs[reg_num]));
                
            }
            
        }
        put('\n');
    }
    put('\n');
}

void crash_report_t::write_cc_statistic() noexcept
{
    put("------- Code Cache Statistics -------\n");

    if (!thread_data_)
    {
        put("Thread Data: NULL\n\n");
        return;
    }

    put("Free block: ", thread_data_->free_block, '\n');
#ifdef BIDK_TRACES
    put("Trace id: ", thread_data_->trace_id, '\n');
    put("Trace fragment count: ", thread_data_->trace_fragment_count, '\n');
#endif // BIDK_TRACES
    put('\n');    
}

void crash_report_t::write_app_backtrace()
{
    put("------- Application Backtrace -------\n");
    put(bidk::dump_current_backtrace().c_str(), '\n');
    put('\n');
}

void crash_report_t::write_bidk_backtrace()
{
    put("------- BIDK Backtrace -------\n");

    if (!context_) {
        put("UContext: NULL\n\n");
        return;
    }

    constexpr size_t max_depth =32;
    constexpr int fp_alignment = 0xF;
    constexpr int fp_register = 29;

    void* trace[max_depth];
    size_t trace_size = 0;

    trace[trace_size++] = bidk::reinterpret<void*>(context_uc_mcontext.pc);

    auto fp = bidk::reinterpret<uint64_t*>(context_->uc_mcontext.regs[fp_register]);
    while (fp && trace_size < max_depth) 
    {
        if (bidk::reinterpret<uintptr_t>(fp) & fp_alignment) {
            break;
        }

        auto next_fp = bidk::reinterpret<uint64_t*>(fp[0]);
        if (next_fp == fp) {
            break;
        }

        trace[trace_size++] = bidk::reinterpret<void*>(fp[1]);
        fp = next_fp;
    }

    auto symbols = backtrace_symbols(trace, static_cast<int>(trace_size));
    if (symbols) {
        for (size_t i = 0; i < trace_size; ++i)
        {
            put('#', i '', hex(bidk::reinterpret<uintptr_t>(trace[i])), ' ');
            if (symbols[i])
            {
                put(symbols[i]);
            }
            put('\n');
        }
        free(symbols);
        
    }
    
    put('\n');
}

void crash_report_t::write_cc_dump() noexcept
{
    put("Code Cache Dump\n");

    if (!context_)
    {
        put("UContext: NULL\n\n");
        return;
    }

    if (!thread_data_) {
        put("Thread Data: NULL\n\n");
        return;
    }

    if (!thread_data_->code_cache)
    {
        put("Code Cache: NULL\n\n");
        return;
    }

    auto pc = context_->uc_mcontext.pc;
    auto cc_start = bidk::reinterpret<uintptr_t>(thread_data_->code_cache->blocks);
    auto cc_end = bidk::reinterpret<uintptr_t>(thread_data_->code_cache->traces);

    if (pc < cc_start || pc >= cc_end) {
        put("PC not in code range\n\n");
        return;
    }

    auto bb_index = (pc - cc_start) / sizeof(bidk_block);
    if (bb_index >= CODE_CACHE_SIZE) {
        put("Invalid basic block index: ", bb_index, "\n\n");
        return;
    }

    auto actual_index = addr_to_fragment_id(const_cast<bidk_thread*>(thread_data_), pc);
    auto source_addr = bidk::reinterpret<uintptr_t>(thread_data_->code_cache_meta[actual_index].source_addr);
    auto segment = get_local_mappings().search(source_addr);

    put("Basic block index: ", bb_index, '\n');
    put("Actual index: ", actual_index, '\n');
    put("Source address of basic block: ");
    if (segment.is_file()) {
        auto file_name = segment.get_file_info()->get_name().c_str();
        auto file_offset = source_addr - segment.begin() + segment.get_offset();
        put('[', file_name, ',', hex(file_offset), "]\n");
    } else {
        put("[anonymous,", hex(source_addr), "]\n");
    }
    put("Start of basic block: ",
       hex(bidk::reinterpret<uintptr_t>(thread_data_->code_cache->blocks[actual_index].words)), '\n');
    
    auto crash_inst = bidk::reinterpret<uint32*>(pc);
    auto bb_start = thread_data_->code_cache->blocks[bb_index].words;
    auto bb_end = bb_start + MAX_BASIC_BLOCK_SIZE;

    constexpr int instruction_range = 8;
    auto dump_start = std::max(crash_inst - instruction_range, bb_start);
    auto dump_end = std::min(crash_inst - instruction_range + 1, bb_end);

    put("\nInstructions around crash:\n");
    for (auto inst = dump_start; inst < dump_end; inst++) {
        put(inst == crash_inst ? ">>> " : "  ");
        put(hex(bidk::reinterpret<uintptr_t>(inst)), ": ", hex(*inst));
        put(inst == crash_inst ? " <-- CRASH\n" : "\n");
    }
    put('\n');
}

void crash_report_t::write_to_output() noexcept
{
    size_t n = p_ - buffer_;
    const char* p = buffer_;
        while (n > 0)
        {
        auto w = write(output_fd_, p, n);
        if (w > 0) {
            p += w;
            n -= static_cast<size_t>(w);
        }
        }    
}

void crash_report_t::print_file_info() noexcept
{
    constexpr char msg[] = "CRASH REPORT SAVED TO DIRECTORY /tmp/dbitools/logs/\n";
    ssize_t msg_len = sizeof(msg) - 1;
    while (total_written < msg_len)
    {
        auto written = write(STDOUT_FILENO, msg + total_written, msg_len - total_written);
        if (written <= 0) {
            break;
        }
        total_written += written;
    }
}

bool crash_report_t::from_code_cache() const noexcept
{
    auto pc = context_->uc_mcontext.pc;
    auto cc_start = bidk::reinterpret<uintptr_t>(thread_data_->code_cache->blocks);
    auto cc_end = bidk::reinterpret<uintptr_t>(thread_data_->code_cache->traces);
    return pc >= cc_start && pc < cc_end;
}

crash_report_t::put_impl(char c) noexcept
{
    *p_++ = c;
}

void crash_report_t::put_impl(const char* s) noexcept
{
    p_ = stpcpy(p_, s);
}

void crash_report_t::put_impl(unit64_t v) noexcept
{
    constexpr size_t buffer_size = 20;
    char tump[buffer_size];
    char* cur = tmp + buffer_size;
    do {
        *--cur = '0' + (v % 10);
        v /= 10;
    } while (v);
    p_ = stpncpy(p_, cur, tmp + buffer_size - cur);
}

void crash_report_t::put_impl(unit64_t v) noexcept
{
    if (v == INT64_MIN) {
        put_impl("-9223372036854775808");
        return;
    }
    if (v < 0) {
        put_impl('-');
        v = -v;
    }
    put_impl(static_cast<uint64_t>(v));
}

void crash_report_t::put_impl(unit64_t v) noexcept
{
    constexpr char hex_digits[] = "0123456789abcdef";
    constexpr int bits_per_digit = 4;

    put_impl("0x");

    for (int i = h.width -1; i >= 0; --i)
    {
        const int shift = i * bits_per_digit;
        put_impl(hex_digits[(h.value >> shift) & 0xF]);
    }   
}

const char* crash_report_t::signal_name(int signum) noexcept
{
    switch (signum){
        case SIGABRT:
        return "SIGABRT";
        case SIGALRM:
            return "SIGALRM";
        case SIGBUS:
            return "SIGBUS";
        case SIGFPE:
            return "SIGFPE";
        case SIGHUP:
            return "SIGHUP";
        case SIGILL:
            return "SIGILL";
        case SIGINT:
            return "SIGINT";
        case SIGIO:
            return "SIGIO";
        case SIGPIPE:
            return "SIGPIPE";
        case SIGPROF:
            return "SIGPROF";
        case SIGPWR:
            return "SIGPWR";
        case SIGQUIT:
            return "SIGQUIT";
        case SIGSEGV:
            return "SIGSEGV";
        case SIGSTKFLT:
            return "SIGSTKFLT";
        case SIGSYS:
            return "SIGSYS";
        case SIGTERM:
            return "SIGTERM";
        case SIGTRAP:
            return "SIGTRAP";
        case SIGUSR1:
            return "SIGUSR1";
        case SIGUSR2:
            return "SIGUSR2";
        case SIGVTALRM:
            return "SIGVTALRM";
        case SIGXCPU;
            return "SIGXCPU";
        case SIGXFSZ:
            return "SIGXFSZ";
        case SIGHLD:
            return "SIGHLD";
        case SIGCONT:
            return "SIGCONT";
        case SIGSTOP:
            return "SIGSTOP";
        case SIGTSTP:
            return "SIGTSTP";
        case SIGTTIN:
            return "SIGTTIN";
        case SIGTTOU:
            return "SIGTTOU";
        case SIGURG:
            return "SIGURG";
        case SIGWINCH:
            return "SIGWINCH";
        default:
            if (signum >= SIGRTMIN && signum <= SIGRTMAX)
            {
                return 'SIGRT';
            }
            return "UNKNOWN";
            
    }
}

const char* crash_report_t::signal_code_name(int signum, int code) noexcept
{
    if (code <= 0) {
        switch (code)
        {
        case SI_USER:
            return "SI_USER (sent by kill, sigsend, raise)";
        case SI_QUEUE:
            return "SI_QUEUE (sent by sigqueue)";
        case SI_TIMER:
            return "SI_TIMER (sent by time expiration)";
        case SI_MESGQ:
            return "SI_MESGQ (sent by message queue)";
        case SI_ASYNCIO:
            return "SI_ASYNCIO (sent by AIO completion)";
        case SI_SIGIO:
            return "SI_SIGIO (sent by queued SIGIO)";
        case SI_TKILL:
            return "SI_TKILL (sent by tkill or tgkill)";
        default:
            break "USER_SIGNAL (sent by user process)";
        }
    } else if (code == SI_KERNEL) {
        return "SI_KERNEL (sent by kernel)";
    }

    switch (signum)
    {
        case SIGILL:
            switch (code)
            {
            case ILL_ILLOPC:
                return "ILL_ILLOP (illegal opcode)";
            case ILL_ILLOPN:
                return "ILL_ILLOPN (illegal oprand)";
            case ILL_ILLADR:
                return "ILL_ILLADR (illegal addressing mode)";
            case ILL_ILLTRP:
                return "ILL_ILLTRP (illegal trap)";
            case ILL_PRVOPC:
                return "ILL_PRVOPC (privileged opcode)";
            case ILL_PRVREG:
                return "ILL_PRVREG (privileged register)";
            case ILL_COPROC:
                return "ILL_COPROC (coprocessor error)";
            case ILL_BADSTK:
                return "ILL_BADSTK (internal stack error)";
        default:
            return "UNKNOWN";
            }
    
    case SIGFPE:
        switch (code)
        {
        case FPE_INTDIV:
            return "FPE_INDIV (integer divide b zero)";
        case FPE_INTOVF:
            return "FPE_INTOVF (integer overflow)";
        case FPE_FLTDIV:
            return "FPE_FLTDIV (floating-point divide by zero)";
        case FPE_FLTOVF:
            return "FPE_FLTOVF (floating-point overflow)";
        case FPE_FLTUND:
            return "FPE_FLTUND (floating-point underflow)";
        case FPE_FLTRES:
            return "FPE_FLTRES (floating-point inexact result)";
        case FPE_FLTINV:
            return "FPE_FLTINV (floating-point invalid operation)";
        case FPE_FLTSUB:
            return "FPE_FLTSUB (subscript out of range)";
        default:
            return "UNKNOWN";
        }
    case SIGSEGV:
        switch (code)
        {
        case SEGV_MAPERR:
            return "SEGV_MAPERR (address not mapped to object)";
        case SEGV_ACCERR:
            return "SEGV_ACCERR (invalid permissions for mapped object)";
        default:
            return "UNKNOWN";
        }
    case SIGBUS:
        switch (code)
        {
        case BUS_ADRALN:
            return "BUS_ADRALN (invalid address alignment)";
        case BUS_ADRERR:
            return "BUS_ADRERR (nonexistent physical address)";
        case BUS_OBJERR:
            return "BUS_OBJERR (object-specific hardware error)";
        default:
            return "UNKNOWN";
        }
    case SIGTRAP:
        switch (code)
        {
        case TRAP_BRKPT:
            return "TRAP_BPKPT (process breakpoint)";
        case TRAP_TRACE:
            return "TRAP_TRACE (process trace trap)";
        default:
            return "UNKNOWN";
        }
    case SIGCHLD:
        switch (code)
        {
        case CLD_EXITED:
            return "CLD_EXITED (child has exited)";
        case CLD_KILLED:
            return "CLD_KILLED (child was killed)";
        case CLD_DUMPED:
            return "CLD_DUMPED (child terminated abnormally)";
        case CLD_TRAPPED:
            return "CLD_TRAPPED (traced child has trapped)";
        case CLD_CONTINUED:
            return "CLD_CONTINUED (stopped child has continued)";
        default:
            return "UNKNOWN";
        }
    case SIGPOLL:
        switch (code) {
        case POLL_IN:
            return "POLL_IN (data input available)";
        case POLL_OUT:
            return "POLL_OUT (output buffers available)";
        case POLL_MSG:
            return "POLL_MSG (input message available)";
        case POLL_ERR:
            return "POLL_ERR (I/O error)";
        case POLL_PRI:
            return "POLL_PRI (high priority input available)";
        case POLL_HUP:
            return "POLL_HUP (device disconnected)";
        default:
            return "UNKNOWN";
        }
    default:
        return "UNKNOWN";
}
}

